package jpa.specs;

import jpa.util.SearchFilter;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import javax.persistence.criteria.*;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;

/**
 * Created by barton on 16-5-11.
 */
public final class Specs {
    public static <T> Specification<T> filter(final Collection<SearchFilter> filters) {
        return new Specification<T>() {
            private final static String TYPE_DATE = "java.util.Date";

            @Override
            public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query,
                                         CriteriaBuilder cb) {
                if (!CollectionUtils.isEmpty(filters)) {
                    List<Predicate> predicates = new ArrayList<>();

                    filters.forEach(filter -> {
                        // 内嵌属性转换,比如Task类中有一个User类,现在要按照User.name作为查询条件,则转换成Task.user.name
                        String[] names = StringUtils.split(filter.fieldName, ",");

                        // 获得的是<T>里边的字段
                        Path expression = root.get(names[0]);

                        for (int i = 1; i < names.length; i++) {
                            expression = expression.get(names[i]);
                        }

                        // 类型名称
                        String javaTypeName = expression.getJavaType().getName();

                        // 逻辑处理
                        switch (filter.operator) {
                            case EQ:
                                if (javaTypeName.equals(TYPE_DATE)) {
                                    predicates.add(cb.greaterThanOrEqualTo(expression, parse
                                            (filter.value, DateBoundary.MIN)));
                                    predicates.add(cb.lessThanOrEqualTo(expression, parse
                                            (filter.value, DateBoundary.MAX)));
                                } else {
                                    predicates.add(cb.equal(expression, filter.value));
                                }
                                break;
                            case NOTEQ: // LIKE, GT, LT, GTE, LTE
                                if (javaTypeName.equals(TYPE_DATE)) {
                                    predicates.add(cb.greaterThanOrEqualTo(expression, parse
                                            (filter.value, DateBoundary.MAX)));
                                    predicates.add(cb.lessThanOrEqualTo(expression, parse
                                            (filter.value, DateBoundary.MIN)));
                                } else {
                                    predicates.add(cb.notEqual(expression, filter.value));
                                }
                                break;
                            case LIKE: // like不支持日期参数
                                predicates.add(cb.like(expression, "%" + filter.value + "%"));
                                break;
                            case GT:
                                if (javaTypeName.equals(TYPE_DATE)) {
                                    predicates.add(cb.greaterThan(expression, parse
                                            (filter.value, DateBoundary.MAX)));
                                } else {
                                    predicates.add(cb.greaterThan(expression, (Comparable) filter
                                            .value));
                                }
                                break;
                            case LT:
                                if (javaTypeName.equals(TYPE_DATE)) {
                                    predicates.add(cb.lessThan(expression, parse
                                            (filter.value, DateBoundary.MAX)));
                                } else {
                                    predicates.add(cb.lessThan(expression, (Comparable) filter
                                            .value));
                                }
                                break;
                            case GTE:
                                if (javaTypeName.equals(TYPE_DATE)) {
                                    predicates.add(cb.greaterThanOrEqualTo(expression, parse
                                            (filter.value, DateBoundary.MAX)));
                                } else {
                                    predicates.add(cb.greaterThanOrEqualTo(expression, (Comparable)
                                            filter.value));
                                }
                                break;
                            case LTE:
                                if (javaTypeName.equals(TYPE_DATE)) {
                                    predicates.add(cb.lessThanOrEqualTo(expression, parse
                                            (filter.value, DateBoundary.MAX)));
                                } else {
                                    predicates.add(cb.lessThanOrEqualTo(expression, (Comparable)
                                            filter.value));
                                }
                                break;
                            case IN:
                                if (javaTypeName.equals(TYPE_DATE)) {
                                    throw new RuntimeException("IN 不支持时间类型的检索条件");
                                } else {
                                    predicates.add(cb.in(expression));
                                }
                                break;
                            default:
                                break;
                        }
                    });
                    // 将所有条件用 and 联合起来
                    if (!predicates.isEmpty()) {
                        return cb.and(predicates.toArray(new Predicate[predicates.size()]));
                    }
                }

                return cb.conjunction();
            }

            private final static String DATE_FORMAT = "yyyy-MM-dd hh:mm:ss SSS";

            private Date parse(Object value, DateBoundary dateBoundary) {
                Date date;
                try {
                    date = new SimpleDateFormat(DATE_FORMAT).parse(value.toString() +
                            dateBoundary.getValue());
                } catch (ParseException e) {
                    throw new RuntimeException("日期格式化失败!");
                }
                return date;
            }
        };
    }

    // 时间边界,是当天的最小,还是当天的最大
    public enum DateBoundary {
        MIN(" 00:00:00 000"), MAX(" 23:59:59 999");

        private String value;

        DateBoundary(String value) {
            this.value = value;
        }

        public String getValue() {
            return this.value;
        }
    }
}